#region Copyright and Licensing Notice
// Copyright (C) 2011 Steven Bosnick
// 
// This program is open source software; you can redistribute it 
// and/or modifiy it under the terms of the Eclipse Public License,
// version 1.0.
// 
// This program is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A 
// PARTICULAR PURPOSE. See the Eclipse Public License for more
// details.
// 
// You should have received a copy of the Eclipse Public License 
// along with this program; if not, you can find a copy at
// "http://www.opensource.org/licenses/eclipse-1.0.php".
#endregion
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using BitwiseOre.IO;
using BitwiseOre.SharedMemory;
using BitwiseOre.SharedMemory.Context;
using NUnit.Framework;

namespace BitwiseOre.Test.SharedMemory.Integration
{
    [TestFixture, Timeout(5000)]
    public sealed class SharedMemoryStreamFixture : AssertionHelper
    {
        private SharedMemoryContext _context;

        [SetUp]
        public void Setup()
        {
            _context = new SharedMemoryContext();
        }

        [Test(Description = "Disconnecting after disposing throws an exception.")]
        public void Disconnect_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerA",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.Disconnect(), Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Disconnecting times out if not acknowleged in time.")]
        public void Disconnect_Timesout()
        {
            const int timeout = 1000;
            var timedout = false;
            var stopwatch = new Stopwatch();

            PairedConnectionTest(
                "ListenerB",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            stopwatch.Start();
                            conn.Disconnect();
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                        finally
                        {
                            stopwatch.Stop();
                        }
                    });

            Expect(timedout, "Disconnecting did not timeout.");
            Expect(stopwatch.ElapsedMilliseconds, EqualTo(timeout).Within(50));
        }

        [Test(Description = "Disconnecting returns sucessfully after the other side reads from the connection.")]
        public void Disconnect_ReturnsAfterReadOnOtherSide(
            [ValueSource("StreamContextValues")] Context context)
        {
            const int timeout = 1000;
            var timedout = false;

            var name = string.Format("ListenerC!{0}!{1}", context.BufferSize, context.ChunckingFactor);
            _context.StreamContext.BufferSize = context.BufferSize;
            _context.StreamContext.ChunckingFactor = context.ChunckingFactor;

            PairedConnectionTest(
                name,
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            conn.Disconnect();
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                    },
                conn =>
                    {
                        var buffer = new byte[5];
                        conn.Read(buffer, 0, buffer.Length);
                    });

            Expect(!timedout, "Disconnect timed out unexpectedly.");
        }

        [Test(Description = "Disconecting after reading returns sucessfully.")]
        public void Disconnect_AfterRead()
        {
            const int timeout = 1000;
            var timedout = false;

            PairedConnectionTest(
                "ListenerD",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            var buffer = new byte[5];
                            conn.Read(buffer, 0, buffer.Length);
                            conn.Disconnect();
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                    },
                conn =>
                    {
                        var buffer = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
                        conn.Write(buffer, 0, buffer.Length);
                        conn.Flush();
                        conn.Read(buffer, 0, buffer.Length);
                    });

            Expect(!timedout, "Disconnect timed out unexpectedly.");
        }

        [Test(Description = "Disconnecting after writing returns sucessfully.")]
        public void Disconnect_AfterWrite()
        {
            const int timeout = 1000;
            var timedout = false;

            PairedConnectionTest(
                "ListenerE",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            var buffer = new byte[] {1, 2, 3, 4, 5};
                            conn.Write(buffer, 0, buffer.Length);
                            conn.Disconnect();
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                    },
                conn =>
                    {
                        var buffer = new byte[10];
                        conn.Read(buffer, 0, buffer.Length);
                        conn.Read(buffer, 0, buffer.Length);
                    });

            Expect(!timedout, "Disconnect timed out unexpectedly.");
        }

        [Test(Description = "Begining to disconnect after disposing throws an exception.")]
        public void BeginDisconnect_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerF",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.BeginDisconnect(null, null),
                               Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Ending disconnecting after disposing throws an exception.")]
        public void EndDisconnect_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerG",
                conn =>
                    {
                        var async = conn.BeginDisconnect(null, null);
                        conn.Dispose();
                        Expect(() => conn.EndDisconnect(async),
                               Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Begining and ending a disconect operation timesout is not acknowleged in time.")]
        public void BeginEndDisconnect_TimesOut()
        {
            const int timeout = 1000;
            var timedout = false;
            var stopwatch = new Stopwatch();

            PairedConnectionTest(
                "ListenerH",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            stopwatch.Start();
                            conn.EndDisconnect(conn.BeginDisconnect(null, null));
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                        finally
                        {
                            stopwatch.Stop();
                        }
                    });

            Expect(timedout, "Begining and ending a disconnect did not timeout.");
            Expect(stopwatch.ElapsedMilliseconds, EqualTo(timeout).Within(50));
        }

        [Test(Description = "Begining and ending a disconnect operation returns sucessfully after the " +
                            "other side reads from the connections.")]
        public void BeginEndDisconnect_ReturnsAfterReadOnOtherSide()
        {
            const int timeout = 1000;
            var timedout = false;

            PairedConnectionTest(
                "ListenerI",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            conn.EndDisconnect(conn.BeginDisconnect(null, null));
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                    },
                conn =>
                    {
                        var buffer = new byte[5];
                        conn.Read(buffer, 0, buffer.Length);
                    });

            Expect(!timedout, "Begining and ending a disconnect timed out unexpectedly.");
        }

        [Test(Description = "Begining and ending a disconnect after reading returns sucessfuly.")]
        public void BegingEndDisconnect_AfterRead()
        {
            const int timeout = 1000;
            var timedout = false;

            PairedConnectionTest(
                "ListenerJ",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            var buffer = new byte[5];
                            conn.Read(buffer, 0, buffer.Length);
                            conn.EndDisconnect(conn.BeginDisconnect(null,null));
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                    },
                conn =>
                    {
                        var buffer = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9};
                        conn.Write(buffer, 0, buffer.Length);
                        conn.Flush();
                        conn.Read(buffer, 0, buffer.Length);
                    });

            Expect(!timedout, "Disconnect timed out unexpectedly.");
        }

        [Test(Description = "Begining and ending a disconnect after writing returns sucessfully.")]
        public void BegingEndDisconnect_AfterWrite()
        {
            const int timeout = 1000;
            var timedout = false;

            PairedConnectionTest(
                "ListenerK",
                conn =>
                    {
                        conn.DisconnectTimeout = timeout;
                        try
                        {
                            var buffer = new byte[] {1, 2, 3, 4, 5};
                            conn.Write(buffer, 0, buffer.Length);
                            conn.EndDisconnect(conn.BeginDisconnect(null, null));
                        }
                        catch (TimeoutException)
                        {
                            timedout = true;
                        }
                    },
                conn =>
                    {
                        var buffer = new byte[10];
                        conn.Read(buffer, 0, buffer.Length);
                        conn.Read(buffer, 0, buffer.Length);
                    });

            Expect(!timedout, "Disconnect timed out unexpectedly.");
        }

        [Test(Description = "Reading into a null buffer throws an exception.")]
        public void Read_WithNullBuffer()
        {
            PairedConnectionTest(
                "ListenerL",
                conn => Expect(() => conn.Read(null, 0, 5), Throws.InstanceOf<ArgumentNullException>()));
        }

        [Test(Description = "Reading with a negative offset throws an exception.")]
        public void Read_WithNegativeOffset()
        {
            PairedConnectionTest(
                "ListenerM",
                conn => Expect(() => conn.Read(new byte[5], -1, 4),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Reading with a negative count throws an exception.")]
        public void Read_WithNegativeCount()
        {
            PairedConnectionTest(
                "ListenerN",
                conn => Expect(() => conn.Read(new byte[5], 0, -2),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Reading with a count and offset past the end of the buffer thows an exception.")]
        public void Read_WithCountAndOffsetPastEndOfBuffer()
        {
            PairedConnectionTest(
                "ListenerO",
                conn => Expect(() => conn.Read(new byte[5], 4, 2),
                               Throws.ArgumentException));
        }

        [Test(Description = "Reading after disposing throws an exception.")]
        public void Read_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerP",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.Read(new byte[5], 0, 3),
                               Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Reading without anything being written to other side times out.")]
        public void Read_TimesOut()
        {
            PairedConnectionTest(
                "ListenerP2",
                conn =>
                    {
                        conn.ReadTimeout = 1000;
                        Expect(() => conn.Read(new byte[5], 0, 5 ), Throws.InstanceOf<TimeoutException>());
                    });
        }

        [Combinatorial,Test(Description = "Reading reads the bytes that are written to the other side.")]
        public void ReadWrite_ReadsWhatIsWrittenToOtherSide(
            [Values(1, 2, 5, 7)] int writesize,
            [Values(1, 2, 5, 7)] int readsize)
        {
            var writearray = (from i in Enumerable.Range(0, writesize)
                              select (byte) i).ToArray();
            var readarray = new byte[readsize];
            var readcount = 0;

            PairedConnectionTest(
                "ListenerQ",
                conn => { readcount = conn.Read(readarray, 0, readarray.Length); },
                conn =>
                    {
                        conn.Write(writearray, 0, writearray.Length);
                        conn.Flush();
                    });

            Expect(readcount, EqualTo(Math.Min(writesize, readsize)));
            Expect(readarray.Take(readcount), EquivalentTo(writearray.Take(readcount)));
        }

        [Timeout(3*60*1000),Test(Description = "Reading reads the bytes that are written no matter what the " +
                                               "underlying IStreamContext is.")]
        public void ReadWrite_ReadsWhatIsWrittenInAnyValidContext(
            [ValueSource("StreamContextValues")] Context context)
        {
            const int readwritesize = 256;
            var chunksize = context.BufferSize/context.ChunckingFactor;
            var count = 3*(chunksize/readwritesize + 1);

            var writearray = Enumerable.Range(0, readwritesize).Select(i => (byte)i).ToArray();
            var readarray = new byte[readwritesize];

            var name = string.Format("ListenerQ1!{0}!{1}", context.BufferSize, context.ChunckingFactor);
            _context.StreamContext.BufferSize = context.BufferSize;
            _context.StreamContext.ChunckingFactor = context.ChunckingFactor;

            var readcount = 0;
            var writecount = 0;

            PairedConnectionTest(
                name,
                conn =>
                    {
                        while (readcount < count * readwritesize)
                        {
                            readcount += conn.Read(readarray, 0, readarray.Length);
                        }
                    },
                conn =>
                    {
                        for (var i = 0; i < count; i++)
                        {
                            conn.Write(writearray, 0, writearray.Length);
                            writecount += writearray.Length;
                        }
                        conn.Flush();
                    });

            Expect(readcount, Not.EqualTo(0));
            Expect(readcount, EqualTo(writecount));
        }

        [Test(Description = "Reading reads the bytes written when the chunking factor is 1.")]
        public void ReadWrite_WithChunkingFactorOne(
            [Values(254, 1)] int bufferSize)
        {
            _context.StreamContext.ChunckingFactor = 1;
            _context.StreamContext.BufferSize = bufferSize;
            var name = string.Format("ListenerQ2!{0}", bufferSize);

            var count = bufferSize * 3;
            var writearray = Enumerable.Range(0, count).Select(i => (byte) (i%byte.MaxValue)).ToArray();
            var readarray = new byte[count];

            PairedConnectionTest(
                name,
                conn =>
                    {
                        var readcount = 0;
                        while (readcount < count)
                        {
                            readcount += conn.Read(readarray, readcount, count - readcount);
                        }
                    },
                conn =>
                    {
                        conn.Write(writearray, 0, writearray.Length);
                        conn.Flush();
                    });

            Expect(readarray, EquivalentTo(writearray), "Reading did not read the bytes that were written.");
        }

        [Combinatorial,Test(Description = "Reading reads the bytes that are written asynchonously to the other side.")]
        public void ReadBeginEndWrite_ReadsWhatIsWrittenToOtherSide(
            [Values(1, 2, 5, 7)] int writesize,
            [Values(1, 2, 5, 7)] int readsize)
        {
            var writearray = (from i in Enumerable.Range(0, writesize)
                              select (byte) i).ToArray();
            var readarray = new byte[readsize];
            var readcount = 0;

            PairedConnectionTest(
                "ListenerQ",
                conn => { readcount = conn.Read(readarray, 0, readarray.Length); },
                conn =>
                    {
                        conn.EndWrite(conn.BeginWrite(writearray, 0, writearray.Length, null, null));
                        conn.Flush();
                    });

            Expect(readcount, EqualTo(Math.Min(writesize, readsize)));
            Expect(readarray.Take(readcount), EquivalentTo(writearray.Take(readcount)));
        }

        [Combinatorial,Test(Description = "Reading reads the bytes that are written asynchonously to the other side.")]
        public void ReadWriteByte_ReadsWhatIsWrittenToOtherSide(
            [Values(1, 2, 5, 7)] int readsize)
        {
            const byte writebyte = (byte) 5;
            var readarray = new byte[readsize];
            var readcount = 0;

            PairedConnectionTest(
                "ListenerQ",
                conn => { readcount = conn.Read(readarray, 0, readarray.Length); },
                conn =>
                    {
                        conn.WriteByte(writebyte);
                        conn.Flush();
                    });

            Expect(readcount, EqualTo(1));
            Expect(readarray[0], EqualTo(writebyte));
        }

        [Test(Description = "Reading signals the ends the stream with a 0 return value when the " +
                            "other side disconnects. ")]
        public void Read_EndsStreamUponDisconnect()
        {
            int? readcount = null;

            PairedConnectionTest(
                "ListenerQ2",
                conn =>  readcount = conn.Read(new byte[5], 0, 5),
                conn => conn.Disconnect());

            Expect(readcount.HasValue, "Read operation did not return a value.");
            Expect(readcount.Value, EqualTo(0));
        }

        [Test(Description = "Begining to read into a null buffer throws an exception.")]
        public void BeginRead_WithNullBuffer()
        {
            PairedConnectionTest(
                "ListenerR",
                conn => Expect(() => conn.BeginRead(null, 0, 5, null, null),
                               Throws.InstanceOf<ArgumentNullException>()));
        }

        [Test(Description = "Begining to read with a negative offset throws an exception.")]
        public void BeginRead_WithNegativeOffset()
        {
            PairedConnectionTest(
                "ListenerS",
                conn => Expect(() => conn.BeginRead(new byte[5], -1, 3, null, null),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Begining to read with a negative count throws an exception.")]
        public void BeginRead_WithNegativeCount()
        {
            PairedConnectionTest(
                "ListenerT",
                conn => Expect(() => conn.BeginRead(new byte[5], 0, -1, null, null),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Begining to read with a count and offset past the end of the buffer " +
                            "throws an exception.")]
        public void BeginRead_WtihCountAdnOffsetPastEndOfBuffer()
        {
            PairedConnectionTest(
                "ListenerU",
                conn => Expect(() => conn.BeginRead(new byte[5], 3, 4, null, null),
                               Throws.InstanceOf<ArgumentException>()));
        }

        [Test(Description = "Begining to read after disposing throws an exception.")]
        public void BeginRead_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerV",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.BeginRead(new byte[5], 0, 5, null, null),
                               Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Ending a read operation after disposing throws an exception.")]
        public void EndRead_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerW",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.EndRead(null), Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Begining and ending a read operation without anything being written to " +
                            "the other side times out.")]
        public void BeginEndRead_TimesOut()
        {
            PairedConnectionTest(
                "ListenerX",
                conn =>
                    {
                        conn.ReadTimeout = 1000;
                        Expect(() => conn.EndRead(conn.BeginRead(new byte[5], 0, 5, null, null)),
                               Throws.InstanceOf<TimeoutException>()
                            );
                    });
        }

        [Combinatorial,Test(Description = "Begining and ending a read operation reads the bytes that are written " +
                            "to the other side.")]
        public void BeginEndRead_ReadWhatIsWrittenToOtherSide(
            [Values(1,2,5,7)] int writeSize,
            [Values(1,2,5,7)] int readSize)
        {
            var writearray = (from i in Enumerable.Range(0, writeSize)
                              select (byte)i).ToArray();
            var readarray = new byte[readSize];
            var readcount = 0;

            PairedConnectionTest(
                "ListenerY",
                conn => { readcount = conn.EndRead(conn.BeginRead(readarray, 0, readarray.Length, null, null)); },
                conn =>
                {
                    conn.Write(writearray, 0, writearray.Length);
                    conn.Flush();
                });

            Expect(readcount, EqualTo(Math.Min(writeSize, readSize)));
            Expect(readarray.Take(readcount), EquivalentTo(writearray.Take(readcount)));
        }

        [Test(Description = "Begining and ending a read operation signals the end of the stream with a 0 " +
                            "return value upon the other side disconnecting.")]
        public void BeginEndRead_EndsStreamUponDisconnect()
        {
            int? readcount = null;

            PairedConnectionTest(
                "ListenerY2",
                conn => readcount = conn.EndRead(conn.BeginRead(new byte[5], 0, 5, null, null)),
                conn => conn.Disconnect());

            Expect(readcount.HasValue, "Begin/End read operation did not return a value.");
            Expect(readcount.Value, EqualTo(0));
        }

        [Test(Description = "Reading a byte after disposing the connection throws an exception.")]
        public void ReadByte_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerZ",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.ReadByte(), Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Reading a byte without anything being written to the other side times out.")]
        public void ReadByte_TimesOut()
        {
            PairedConnectionTest(
                "ListenerAA",
                conn =>
                    {
                        conn.ReadTimeout = 1000;
                        Expect(() => conn.ReadByte(), Throws.InstanceOf<TimeoutException>());
                    });
        }

        [Test(Description = "Reading a byte reads the first byte that is written to the other side.")]
        public void ReadByte_ReadsWhatIsWrittenToOtherSize()
        {
            var writearray = new byte[] {5, 1, 2, 3};
            var readbyte = 0;

            PairedConnectionTest(
                "ListenerBB",
                conn =>
                    {
                        readbyte = conn.ReadByte();
                    },
                conn =>
                    {
                        conn.Write(writearray, 0, writearray.Length);
                        conn.Flush();
                    });

            Expect(readbyte, EqualTo((int)writearray[0]));
        }

        [Test(Description = "Reading a byte signals the end of the stream with a -1 return value upong the " +
                            "other side disconnecting.")]
        public void ReadByte_EndsStreamUponDisconnect()
        {
            int? readvalue = null;

            PairedConnectionTest(
                "ListenerCC",
                conn => readvalue = conn.ReadByte(),
                conn => conn.Disconnect());

            Expect(readvalue.HasValue, "ReadByte operation did not return a value.");
            Expect(readvalue.Value, EqualTo(-1));
        }

        [Test(Description = "Writing from a null buffer throws an exception.")]
        public void Write_WithNullBuffer()
        {
            PairedConnectionTest(
                "ListenerDD",
                conn => Expect(() => conn.Write(null, 0, 5), Throws.InstanceOf<ArgumentNullException>()));
        }

        [Test(Description = "Writing with a negative offset throws an exception.")]
        public void Write_WithNegativeOffset()
        {
            PairedConnectionTest(
                "ListenerEE",
                conn => Expect(() => conn.Write(new byte[5], -1, 3),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Writing with a negative cunt throws an exception.")]
        public void Write_WtihNegativeCount()
        {
            PairedConnectionTest(
                "ListenerFF",
                conn => Expect(() => conn.Write(new byte[5], 0, -1),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Writing with a count and offset past the end of the buffer throws an exception.")]
        public void Write_WithCountAndOffsetPaseEndOfBuffer()
        {
            PairedConnectionTest(
                "ListenerGG",
                conn => Expect(() => conn.Write(new byte[5], 3, 4),
                               Throws.InstanceOf<ArgumentException>()));
        }

        [Test(Description = "Writing after disposing throws an exception.")]
        public void Write_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerHH",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.Write(new byte[5], 0, 5),
                               Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Writing more than the available buffer without anything being read from " +
                            "the other side times out.")]
        public void Write_TimesOut()
        {
            var writearray = (from i in Enumerable.Range(0, 6)
                              select (byte) i).ToArray();
            _context.StreamContext.BufferSize = 4;
            _context.StreamContext.ChunckingFactor = 2;

            PairedConnectionTest(
                "ListenerII",
                conn =>
                    {
                        conn.WriteTimeout = 1000;
                        Expect(() => conn.Write(writearray, 0, writearray.Length),
                               Throws.InstanceOf<TimeoutException>());
                    });
        }

        [Test(Description = "Begining to write from a null buffer throws an exception.")]
        public void BeginWrite_WithNullBuffer()
        {
            PairedConnectionTest(
                "ListenerJJ",
                conn => Expect(() => conn.BeginWrite(null, 0, 5, null, null),
                               Throws.InstanceOf<ArgumentNullException>()));
        }

        [Test(Description = "Begining to write with a negative offset throws an exception.")]
        public void BeginWrite_WithNegativeOffset()
        {
            PairedConnectionTest(
                "ListenerKK",
                conn => Expect(() => conn.BeginWrite(new byte[5], -1, 3, null, null),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Begining to write with a negative count throws an exception.")]
        public void BeginWrite_WithNegativeCount()
        {
            PairedConnectionTest(
                "ListenerLL",
                conn => Expect(() => conn.BeginWrite(new byte[5], 0, -1, null, null),
                               Throws.InstanceOf<ArgumentOutOfRangeException>()));
        }

        [Test(Description = "Begining to write with a count and offset past the end of the buffer throws " +
                            "an exception.")]
        public void BeginWrite_WithCountAndOffsetPastEndOfBuffer()
        {
            PairedConnectionTest(
                "ListenerMM",
                conn => Expect(() => conn.BeginWrite(new byte[5], 3, 4, null, null),
                               Throws.ArgumentException));
        }

        [Test(Description = "Begining to write after disposing the connection throws an exception.")]
        public void BeginWrite_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerNN",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.BeginWrite(new byte[5], 0, 5, null, null),
                               Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Ending a write operation after disposing the connection throws an exception.")]
        public void EndWrite_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerOO",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.EndWrite(null), Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Begining and ending a write operation to write more than the available buffer" +
                            "without anything being read from the other side times out.")]
        public void BeginEndWrite_TimesOut()
        {
            var writearray = (from i in Enumerable.Range(0, 6)
                              select (byte)i).ToArray();
            _context.StreamContext.BufferSize = 4;
            _context.StreamContext.ChunckingFactor = 2;

            PairedConnectionTest(
                "ListenerPP",
                conn =>
                {
                    conn.WriteTimeout = 1000;
                    Expect(() => conn.EndWrite(conn.BeginWrite(writearray, 0, writearray.Length, null, null)),
                           Throws.InstanceOf<TimeoutException>());
                });
            
        }

        [Test(Description = "Writing a byte after disposing the connection throws an exception.")]
        public void WriteByte_AfterDispose()
        {
            PairedConnectionTest(
                "ListenerQQ",
                conn =>
                    {
                        conn.Dispose();
                        Expect(() => conn.WriteByte(1), Throws.InstanceOf<ObjectDisposedException>());
                    });
        }

        [Test(Description = "Writing a byte when previous writes have written more than the available buffer" +
                            "without anything being read from the other side times out.")]
        public void WriteByte_TimesOut()
        {
            var writearray = (from i in Enumerable.Range(0, 4)
                              select (byte)i).ToArray();
            _context.StreamContext.BufferSize = 4;
            _context.StreamContext.ChunckingFactor = 2;
            
            PairedConnectionTest(
                "ListenerRR",
                conn =>
                    {
                        conn.WriteTimeout = 1000;
                        conn.Write(writearray, 0, writearray.Length);
                        Expect(() => conn.WriteByte(0xff), Throws.InstanceOf<TimeoutException>());
                    });
        }


        private void PairedConnectionTest(string name,
                                          Action<DisconnectableStream> testFactorySide = null,
                                          Action<DisconnectableStream> testListenerSide = null,
                                          Action<DisconnectableStream> cleanupFactorySide = null,
                                          Action<DisconnectableStream> cleanupListenerSide = null
            )
        {
            testFactorySide = testFactorySide ?? (_ => { });
            testListenerSide = testListenerSide ?? (_ => { });
            cleanupFactorySide = cleanupFactorySide ?? (conn => conn.Dispose());
            cleanupListenerSide = cleanupListenerSide ?? (conn => conn.Dispose());

            var barrier = new Barrier(2);

            using (var listener = SharedMemoryListener.Create(_context, name))
            using (var factory = SharedMemoryFactory.Create(_context, name))
            {
                listener.BeginAccept(asyncresult =>
                                         {
                                             var lconn = listener.EndAccept(asyncresult);
                                             testListenerSide(lconn);
                                             barrier.SignalAndWait();
                                             cleanupListenerSide(lconn);
                                             barrier.SignalAndWait();
                                         }, null);

                var fconn = factory.Connect();
                testFactorySide(fconn);
                barrier.SignalAndWait();
                cleanupFactorySide(fconn);
                barrier.SignalAndWait();
            }

        }

        public struct Context : IStreamContext
        {
            public int BufferSize { get; set; }
            public int ChunckingFactor { get; set; }

            public override string ToString()
            {
                return string.Format("BufferSize: {0}, ChunckingFactor: {1}", BufferSize, ChunckingFactor);
            }
        }

        public static IEnumerable<Context> StreamContextValues()
        {
            var buffersizes = new[] {4}
                .Concat(Enumerable.Range(4, 10).Select(i => 1 << i).SelectMany(i => Enumerable.Range(i - 1, 3)));

            return from buffersize in buffersizes
                   select new Context {BufferSize = buffersize, ChunckingFactor = 4};
        }
    }

}